#pragma once
#include "CGIVulkanSwapChain.h"
#include "CGIVulkanResource.h"
#include "Windows/VulkanWindowsPlatform.h"
#include "CGIVulkan.h"

namespace Alice{
    CGIVulkanSwapChain::CGIVulkanSwapChain(VkInstance inInstance, CGIVulkanDevice& inDevice, void* inWindowHandle, EPixelFormat& inOutPixelFormat,
        uint32 inWidth, uint32 inHeight, bool bIsFullScreen,uint32* inOutDesiredNumBackBuffers, std::vector<VkImage>& inOutImages,
        int8 inLockToVsync, VulkanSwapChainRecreateInfo* inRecreateInfo)
    : mSwapChain(VK_NULL_HANDLE)
    , mDevice(inDevice)
    , mSurface(VK_NULL_HANDLE)
    , mWindowHandle(inWindowHandle)
    , mCurrentImageIndex(-1)
    , mSemaphoreIndex(-1)
    , mNumPresentCalls(0)
    , mNumAcquireCalls(0)
    , mInstance(inInstance)
    , mLockToVSync(inLockToVsync)
    {
        mPreTransform=VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
        if(inRecreateInfo!=nullptr&&inRecreateInfo->mSwapChain!=VK_NULL_HANDLE)
        {
            mSurface=inRecreateInfo->mSurface;
            inRecreateInfo->mSurface=VK_NULL_HANDLE;
        }else{
            VulkanWindowsPlatform::CreateSurface(inWindowHandle,inInstance,&mSurface);
        }
        uint32 numFormats;
        vkGetPhysicalDeviceSurfaceFormatsKHR(mDevice.GetPhysicalHandle(), mSurface, &numFormats, nullptr);
        std::vector<VkSurfaceFormatKHR> formats;
        formats.resize(numFormats);
        vkGetPhysicalDeviceSurfaceFormatsKHR(mDevice.GetPhysicalHandle(), mSurface, &numFormats, formats.data());

        VkSurfaceFormatKHR currentFormat;
        VkColorSpaceKHR requestedColorSpace=VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
        VkFormat format=VK_FORMAT_B8G8R8A8_UNORM;
        if (inOutPixelFormat == EPixelFormat::PF_Unknown)
        {
            for (uint32 Index = 0; Index < formats.size(); ++Index)
            {
                // Reverse lookup
                for (int32 PFIndex = 0; PFIndex < PF_MAX; ++PFIndex)
                {
                    if (formats[Index].format == GPixelFormats[PFIndex].PlatformFormat && formats[Index].colorSpace == requestedColorSpace)
                    {
                        inOutPixelFormat = (EPixelFormat)PFIndex;
                        currentFormat = formats[Index];
                        break;
                    }
                }

                if (inOutPixelFormat != EPixelFormat::PF_Unknown)
                {
                    break;
                }
            }
        }
        mDevice.SetupPresentQueue(mSurface);
        VkPresentModeKHR PresentMode = VK_PRESENT_MODE_FIFO_KHR;
        uint32 NumFoundPresentModes = 0;
        vkGetPhysicalDeviceSurfacePresentModesKHR(mDevice.GetPhysicalHandle(), mSurface, &NumFoundPresentModes, nullptr);

        std::vector<VkPresentModeKHR> FoundPresentModes;
        FoundPresentModes.resize(NumFoundPresentModes);
        vkGetPhysicalDeviceSurfacePresentModesKHR(mDevice.GetPhysicalHandle(), mSurface, &NumFoundPresentModes, FoundPresentModes.data());
        bool bFoundPresentModeMailbox = false;
        bool bFoundPresentModeImmediate = false;
        bool bFoundPresentModeFIFO = false;
        for (size_t i = 0; i < NumFoundPresentModes; i++)
        {
            switch (FoundPresentModes[i])
            {
            case VK_PRESENT_MODE_MAILBOX_KHR:
                bFoundPresentModeMailbox = true;
                break;
            case VK_PRESENT_MODE_IMMEDIATE_KHR:
                bFoundPresentModeImmediate = true;
                break;
            case VK_PRESENT_MODE_FIFO_KHR:
                bFoundPresentModeFIFO = true;
                break;
            case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
                break;
            default:
                break;
            }
        }
        if (bFoundPresentModeImmediate && (!mLockToVSync))
        {
            PresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
        }
        else if (bFoundPresentModeMailbox)
        {
            PresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
        }
        else if (bFoundPresentModeFIFO)
        {
            PresentMode = VK_PRESENT_MODE_FIFO_KHR;
        }
        else
        {
            PresentMode = FoundPresentModes[0];
        }
        VkSurfaceCapabilitiesKHR SurfProperties;
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mDevice.GetPhysicalHandle(),mSurface,&SurfProperties);
        VkCompositeAlphaFlagBitsKHR CompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
        if (SurfProperties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
        {
            CompositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        }
        VkSwapchainCreateInfoKHR SwapChainCreateInfo={};
        SwapChainCreateInfo.sType=VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        SwapChainCreateInfo.surface = mSurface;
        SwapChainCreateInfo.minImageCount = 3;
        SwapChainCreateInfo.imageFormat = format;
        SwapChainCreateInfo.imageColorSpace = requestedColorSpace;
        SwapChainCreateInfo.imageExtent.width = inWidth;
        SwapChainCreateInfo.imageExtent.height = inHeight;
        SwapChainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
        SwapChainCreateInfo.preTransform = mPreTransform;
        SwapChainCreateInfo.imageArrayLayers = 1;
        SwapChainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        SwapChainCreateInfo.presentMode = PresentMode;
        SwapChainCreateInfo.oldSwapchain = VK_NULL_HANDLE;
        SwapChainCreateInfo.clipped = VK_TRUE;
        SwapChainCreateInfo.compositeAlpha = CompositeAlpha;
        VkBool32 bSupportsPresent;
        vkGetPhysicalDeviceSurfaceSupportKHR(mDevice.GetPhysicalHandle(), mDevice.GetPresentQueue()->GetFamilyIndex(), mSurface, &bSupportsPresent);
        vkCreateSwapchainKHR(mDevice.GetDevice(), &SwapChainCreateInfo, NULL, &mSwapChain);
        uint32 NumSwapChainImages;
        vkGetSwapchainImagesKHR(mDevice.GetDevice(), mSwapChain, &NumSwapChainImages, nullptr);
        inOutImages.resize(NumSwapChainImages);
        vkGetSwapchainImagesKHR(mDevice.GetDevice(), mSwapChain, &NumSwapChainImages, inOutImages.data());
        mImageAcquiredSemaphores.resize(NumSwapChainImages);
        *inOutDesiredNumBackBuffers=NumSwapChainImages;
        for (uint32 BufferIndex = 0; BufferIndex < NumSwapChainImages; ++BufferIndex)
        {
            mImageAcquiredSemaphores[BufferIndex] = new VulkanSemaphore(mDevice);
        }
    }
    int32 CGIVulkanSwapChain::AcquireImageIndex(VulkanSemaphore** OutSemaphore)
    {
        uint32 ImageIndex = 0;
        const int32 PrevSemaphoreIndex = mSemaphoreIndex;
        mSemaphoreIndex = (mSemaphoreIndex + 1) % 3;
        vkAcquireNextImageKHR(
            mDevice.GetDevice(),
            mSwapChain,
            UINT64_MAX,
            mImageAcquiredSemaphores[mSemaphoreIndex]->GetHandle(),
            nullptr,
            &ImageIndex);
        VkSemaphore readyToShow;
        VkSemaphoreCreateInfo semaphoreCreateInfo = {};
        semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
        vkCreateSemaphore(mDevice.GetDevice(), &semaphoreCreateInfo, nullptr, &readyToShow);
        {
            const VkSemaphore readyToRender=mImageAcquiredSemaphores[mSemaphoreIndex]->GetHandle();
            //submit
            VkPipelineStageFlags waitStage[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
            VkSubmitInfo submitInfo = {};
            submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
            submitInfo.commandBufferCount = 0;
            submitInfo.pCommandBuffers = nullptr;
            submitInfo.waitSemaphoreCount = 1;
            submitInfo.pWaitSemaphores = &readyToRender;
            submitInfo.pWaitDstStageMask = waitStage;
            submitInfo.signalSemaphoreCount = 1;
            submitInfo.pSignalSemaphores = &readyToShow;
            vkQueueSubmit(mDevice.mGraphicsQueue->Queue, 1, &submitInfo, nullptr);//cpu -> gpu
        }
        VkPresentInfoKHR presentInfoKHR = {};
        presentInfoKHR.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
        presentInfoKHR.waitSemaphoreCount = 1;
        presentInfoKHR.pWaitSemaphores = &readyToShow;
        presentInfoKHR.pSwapchains = &mSwapChain;
        presentInfoKHR.swapchainCount = 1;
        presentInfoKHR.pImageIndices = &ImageIndex;
        vkQueuePresentKHR(mDevice.mPresentQueue->Queue, &presentInfoKHR);
        vkQueueWaitIdle(mDevice.mPresentQueue->Queue);
        return ImageIndex;
    }

}
